package org.ecype.diego;

import java.io.FileNotFoundException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.ecype.diego.sqlite.DatabaseHelper;

import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import androidx.persistence.access.Incrementer;
import androidx.persistence.access.Marker;
import androidx.persistence.access.Subdirectory;
import androidx.persistence.access.UriProcessor;
import androidx.persistence.access.Access;

public abstract class ContractContentProvider extends ContentProvider {

	private static final String TAG = "Contract";
	private final Class<?> contractClass;
	//TODO this reference can be avoided 
	private Contract contract;
	private DatabaseHelper helper;

	private UriMatcher uris;
	private List<UriProcessor> processors;
	private int code;
	private ContentResolver resolver;

	private static final Map<Class<? extends Annotation>, Class<? extends UriProcessor>> KNOWN_URI_CLASSES;

	static {
		KNOWN_URI_CLASSES = new HashMap<Class<? extends Annotation>, Class<? extends UriProcessor>>();
		KNOWN_URI_CLASSES.put(Access.class, null);
		KNOWN_URI_CLASSES.put(Subdirectory.class, SubdirectoryProcessor.class);
		KNOWN_URI_CLASSES.put(Marker.class, MarkerProcessor.class);
		KNOWN_URI_CLASSES.put(Incrementer.class, IncrementerProcessor.class);
	}

	protected ContractContentProvider(Class<?> contract) {
		this.contractClass = contract;
	}

	@Override
	public boolean onCreate() {
		try {
			resolver = getContext().getContentResolver();
			contract = new Contract(contractClass);
			helper = new DatabaseHelper(getContext(), contract);
			uris = new UriMatcher(UriMatcher.NO_MATCH);
			processors = new ArrayList<UriProcessor>();
			code = 0;
			
			for (Entity entity : contract) {
				String name = entity.getName();
				registerProcessor(name, new EntityDirProcessor(entity), null);
				registerProcessor(name + "/#", new EntityItemProcessor(entity), null);

				for (Reference reference : entity.getReferencesToChild()) {
					if (reference.getLinkToChild() != null) {
						registerProcessor(name + "/#/" + reference.getLinkToChild(), new OneToManyProcessor(reference), reference.getField());
					}
				}

				for (Reference reference : entity.getReferencesToParent()) {
					//NOTE OneToOne попадает сюда же
					registerProcessor(name + "/#/" + reference.getLinkToParent(), new ManyToOneProcessor(reference), reference.getField());
				}

				Class<?> entityClass = entity.getEntryClass();
				for (Entry<Class<? extends Annotation>, Class<? extends UriProcessor>> uriClass : KNOWN_URI_CLASSES.entrySet()) {
					Class<? extends Annotation> annotation = uriClass.getKey();
					for (Field field : Annotations.extractAnnotatedFields(entityClass, annotation)) {
						final String path;
						Object value = field.get(null);
						if (value instanceof Uri) {
							Uri uri = (Uri) value;
							path = uri.getPath()./*skip '/' at start*/substring(1);
						} else {
							path = name + "/#/" + value.toString();
						}

						Class<? extends UriProcessor> processor;
						if (annotation.equals(Access.class)) {
							processor = field.getAnnotation(Access.class).processor();
						} else {
							processor = uriClass.getValue();
						}
						registerProcessor(path, processor.newInstance(), field);
					}
				}
			}
			
			return true;
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void registerProcessor(String path, UriProcessor processor, Field field) {
		Log.i(TAG, "register path: " + path);
		processor.setup(this, field);
		uris.addURI(contract.getAuthority(), path, code++);
		processors.add(processor);
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		return getProcessor(uri).insert(uri, uri, values);
	}

	public Uri insert(Uri uri, Uri notifyUri, ContentValues values) {
		return getProcessor(uri).insert(uri, notifyUri, values);
	}
	
	private UriProcessor getProcessor(Uri uri) {
		int index = uris.match(uri);
		if (index >= 0) {
			UriProcessor processor = processors.get(index);
			return processor;
		} else {
			throw new IllegalArgumentException("Could not find processor for uri: " + uri.toString());
		}
	}

	@Override
	public String getType(Uri uri) {
		return getProcessor(uri).getType(uri);
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		return query(uri, uri, projection, selection, selectionArgs, sortOrder);
	}
	
	public Cursor query(Uri uri, Uri notifyUri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		return getProcessor(uri).query(uri, notifyUri, projection, selection, selectionArgs, sortOrder);
	}
	
	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		return getProcessor(uri).delete(uri, uri, selection, selectionArgs);
	}

	public int delete(Uri uri, Uri notifyUri, String selection, String[] selectionArgs) {
		return getProcessor(uri).delete(uri, notifyUri, selection, selectionArgs);
	}
	
	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		return getProcessor(uri).update(uri, uri, values, selection, selectionArgs);
	}

	public int update(Uri uri, Uri notifyUri, ContentValues values, String selection, String[] selectionArgs) {
		return getProcessor(uri).update(uri, notifyUri, values, selection, selectionArgs);
	}
	
	@Override
	public int bulkInsert(Uri uri, ContentValues[] values) {
		return getProcessor(uri).bulkInsert(uri, uri, values);
	}

	public int bulkInsert(Uri uri, Uri notifyUri, ContentValues[] values) {
		return getProcessor(uri).bulkInsert(uri, notifyUri, values);
	}
	
	@Override
    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
            throws OperationApplicationException {
	    SQLiteDatabase database = helper.getWritableDatabase();
        database.beginTransaction();
        try {
            ContentProviderResult[] result = super.applyBatch(operations);
            database.setTransactionSuccessful();
            return result;
        } finally {
            database.endTransaction();
        }
    }
	
	@Override
	public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
		throw new UnsupportedOperationException("soon");
	}

	public DatabaseHelper getHelper() {
		return helper;
	}

	public ContentResolver getResolver() {
		return resolver;
	}

	public UriMatcher getUriMatcher() {
		return uris;
	}
}
